<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_8</span></h2>

  <p>也接收从Instrument衍生出来的所有东西。当一个Wind句柄传递给tune()的时候，就会出现这种情况。此时没有造型的必要。这样做是可以接受的；Instrument里的接口必须存在于Wind中，因为Wind是从Instrument里继承得到的。从Wind向Instrument的上溯造型可能“缩小”那个接口，但不可能把它变得比Instrument的完整接口还要小。</p>

  <p>7.1.1 为什么要上溯造型</p>

  <p>这个程序看起来也许显得有些奇怪。为什么所有人都应该有意忘记一个对象的类型呢？进行上溯造型时，就可能产生这方面的疑惑。而且如果让tune()简单地取得一个Wind句柄，将其作为自己的自变量使用，似乎会更加简单、直观得多。但要注意：假如那样做，就需为系统内Instrument的每种类型写一个全新的tune()。假设按照前面的推论，加入Stringed（弦乐）和Brass（铜管）这两种Instrument（乐器）：</p>

  <p>//: Music2.java</p>

  <p>// Overloading instead of upcasting</p>

  <p>class Note2 {</p>

  <p>private int value;</p>

  <p>private Note2(int val) { value = val; }</p>

  <p>public static final Note2</p>

  <p>middleC = new Note2(0),</p>

  <p>cSharp = new Note2(1),</p>

  <p>cFlat = new Note2(2);</p>

  <p>} // Etc.</p>

  <p>class Instrument2 {</p>

  <p>public void play(Note2 n) {</p>

  <p>System.out.println("Instrument2.play()");</p>

  <p>}</p>

  <p>}</p>

  <p>class Wind2 extends Instrument2 {</p>

  <p>public void play(Note2 n) {</p>

  <p>System.out.println("Wind2.play()");</p>

  <p>}</p>

  <p>}</p>

  <p>class Stringed2 extends Instrument2 {</p>

  <p>public void play(Note2 n) {</p>

  <p>System.out.println("Stringed2.play()");</p>

  <p>}</p>

  <p>}</p>

  <p>class Brass2 extends Instrument2 {</p>

  <p>public void play(Note2 n) {</p>

  <p>System.out.println("Brass2.play()");</p>

  <p>}</p>

  <p>}</p>

  <p>public class Music2 {</p>

  <p>public static void tune(Wind2 i) {</p>

  <p>i.play(Note2.middleC);</p>

  <p>}</p>

  <p>public static void tune(Stringed2 i) {</p>

  <p>i.play(Note2.middleC);</p>

  <p>}</p>

  <p>public static void tune(Brass2 i) {</p>

  <p>i.play(Note2.middleC);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Wind2 flute = new Wind2();</p>

  <p>Stringed2 violin = new Stringed2();</p>

  <p>Brass2 frenchHorn = new Brass2();</p>

  <p>tune(flute); // No upcasting</p>

  <p>tune(violin);</p>

  <p>tune(frenchHorn);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这样做当然行得通，但却存在一个极大的弊端：必须为每种新增的Instrument2类编写与类紧密相关的方法。这意味着第一次就要求多得多的编程量。以后，假如想添加一个象tune()那样的新方法或者为Instrument添加一个新类型，仍然需要进行大量编码工作。此外，即使忘记对自己的某个方法进行过载设置，编译器也不会提示任何错误。这样一来，类型的整个操作过程就显得极难管理，有失控的危险。</p>

  <p>但假如只写一个方法，将基础类作为自变量或参数使用，而不是使用那些特定的衍生类，岂不是会简单得多？也就是说，如果我们能不顾衍生类，只让自己的代码与基础类打交道，那么省下的工作量将是难以估计的。</p>

  <p>这正是“多形性”大显身手的地方。然而，大多数程序员（特别是有程序化编程背景的）对于多形性的工作原理仍然显得有些生疏。</p>

  <p>7.2 深入理解</p>

  <p>对于Music.java的困难性，可通过运行程序加以体会。输出是Wind.play()。这当然是我们希望的输出，但它看起来似乎并不愿按我们的希望行事。请观察一下tune()方法：</p>

  <p>public static void tune(Instrument i) {</p>

  <p>// ...</p>

  <p>i.play(Note.middleC);</p>

  <p>}</p>

  <p>它接收Instrument句柄。所以在这种情况下，编译器怎样才能知道Instrument句柄指向的是一个Wind，而不是一个Brass或Stringed呢？编译器无从得知。为了深入了理解这个问题，我们有必要探讨一下“绑定”这个主题。</p>

  <p>7.2.1 方法调用的绑定</p>

  <p>将一个方法调用同一个方法主体连接到一起就称为“绑定”（Binding）。若在程序运行以前执行绑定（由编译器和链接程序，如果有的话），就叫作“早期绑定”。大家以前或许从未听说过这个术语，因为它在任何程序化语言里都是不可能的。C编译器只有一种方法调用，那就是“早期绑定”。</p>

  <p>上述程序最令人迷惑不解的地方全与早期绑定有关，因为在只有一个Instrument句柄的前提下，编译器不知道具体该调用哪个方法。</p>

  <p>解决的方法就是“后期绑定”，它意味着绑定在运行期间进行，以对象的类型为基础。后期绑定也叫作“动态绑定”或“运行期绑定”。若一种语言实现了后期绑定，同时必须提供一些机制，可在运行期间判断对象的类型，并分别调用适当的方法。也就是说，编译器此时依然不知道对象的类型，但方法调用机制能自己去调查，找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为：它们都要在对象中安插某些特殊类型的信息。</p>

  <p>Java中绑定的所有方法都采用后期绑定技术，除非一个方法已被声明成final。这意味着我们通常不必决定是否应进行后期绑定――它是自动发生的。</p>

  <p>为什么要把一个方法声明成final呢？正如上一章指出的那样，它能防止其他人覆盖那个方法。但也许更重要的一点是，它可有效地“关闭”动态绑定，或者告诉编译器不需要进行动态绑定。这样一来，编译器就可为final方法调用生成效率更高的代码。</p>

  <p>7.2.2 产生正确的行为</p>

  <p>知道Java里绑定的所有方法都通过后期绑定具有多形性以后，就可以相应地编写自己的代码，令其与基础类沟通。此时，所有的衍生类都保证能用相同的代码正常地工作。或者换用另一种方法，我们可以“将一条消息发给一个对象，让对象自行判断要做什么事情。”</p>

  <p>在面向对象的程序设计中，有一个经典的“形状”例子。由于它很容易用可视化的形式表现出来，所以经常都用它说明问题。但很不幸的是，它可能误导初学者认为OOP只是为图形化编程设计的，这种认识当然是错误的。</p>

  <p>形状例子有一个基础类，名为Shape；另外还有大量衍生类型：Circle（圆形），Square（方形），Triangle（三角形）等等。大家之所以喜欢这个例子，因为很容易理解“圆属于形状的一种类型”等概念。下面这幅继承图向我们展示了它们的关系：</p>

  <p>上溯造型可用下面这个语句简单地表现出来：</p>

  <p>Shape s = new Circle();</p>

  <p>在这里，我们创建了Circle对象，并将结果句柄立即赋给一个Shape。这表面看起来似乎属于错误操作（将一种类型分配给另一个），但实际是完全可行的――因为按照继承关系，Circle属于Shape的一种。因此编译器认可上述语句，不会向我们提示一条出错消息。</p>

  <p>当我们调用其中一个基础类方法时（已在衍生类里覆盖）：</p>

  <p>s.draw();</p>

  <p>同样地，大家也许认为会调用Shape的draw()，因为这毕竟是一个Shape句柄。那么编译器怎样才能知道该做其他任何事情呢？但此时实际调用的是Circle.draw()，因为后期绑定已经介入（多形性）。</p>

  <p>下面这个例子从一个稍微不同的角度说明了问题：</p>

  <p>//: Shapes.java</p>

  <p>// Polymorphism in Java</p>

  <p>class Shape {</p>

  <p>void draw() {}</p>

  <p>void erase() {}</p>

  <p>}</p>

  <p>class Circle extends Shape {</p>

  <p>void draw() {</p>

  <p>System.out.println("Circle.draw()");</p>

  <p>}</p>

  <p>void erase() {</p>

  <p>System.out.println("Circle.erase()");</p>

  <p>}</p>

  <p>}</p>

  <p>class Square extends Shape {</p>

  <p>void draw() {</p>

  <p>System.out.println("Square.draw()");</p>

  <p>}</p>

  <p>void erase() {</p>

  <p>System.out.println("Square.erase()");</p>

  <p>}</p>

  <p>}</p>

  <p>class Triangle extends Shape {</p>

  <p>void draw() {</p>

  <p>System.out.println("Triangle.draw()");</p>

  <p>}</p>

  <p>void erase() {</p>

  <p>System.out.println("Triangle.erase()");</p>

  <p>}</p>

  <p>}</p>

  <p>public class Shapes {</p>

  <p>public static Shape randShape() {</p>

  <p>switch((int)(Math.random() * 3)) {</p>

  <p>default: // To quiet the compiler</p>

  <p>case 0: return new Circle();</p>

  <p>case 1: return new Square();</p>

  <p>case 2: return new Triangle();</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Shape[] s = new Shape[9];</p>

  <p>// Fill up the array with shapes:</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i] = randShape();</p>

  <p>// Make polymorphic method calls:</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i].draw();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>针对从Shape衍生出来的所有东西，Shape建立了一个通用接口――也就是说，所有（几何）形状都可以描绘和删除。衍生类覆盖了这些定义，为每种特殊类型的几何形状都提供了独一无二的行为。</p>

  <p>在主类Shapes里，包含了一个static方法，名为randShape()。它的作用是在每次调用它时为某个随机选择的Shape对象生成一个句柄。请注意上溯造型是在每个return语句里发生的。这个语句取得指向一个Circle，Square或者Triangle的句柄，并将其作为返回类型Shape发给方法。所以无论什么时候调用这个方法，就绝对没机会了解它的具体类型到底是什么，因为肯定会获得一个单纯的Shape句柄。</p>

  <p>main()包含了Shape句柄的一个数组，其中的数据通过对randShape()的调用填入。在这个时候，我们知道自己拥有Shape，但不知除此之外任何具体的情况（编译器同样不知）。然而，当我们在这个数组里步进，并为每个元素调用draw()的时候，与各类型有关的正确行为会魔术般地发生，就象下面这个输出示例展示的那样：</p>

  <p>Circle.draw()</p>

  <p>Triangle.draw()</p>

  <p>Circle.draw()</p>

  <p>Circle.draw()</p>

  <p>Circle.draw()</p>

  <p>Square.draw()</p>

  <p>Triangle.draw()</p>

  <p>Square.draw()</p>

  <p>Square.draw()</p>

  <p>当然，由于几何形状是每次随机选择的，所以每次运行都可能有不同的结果。之所以要突出形状的随机选择，是为了让大家深刻体会这一点：为了在编译的时候发出正确的调用，编译器毋需获得任何特殊的情报。对draw()的所有调用都是通过动态绑定进行的。</p>

  <p>7.2.3 扩展性</p>

  <p>现在，让我们仍然返回乐器（Instrument）示例。由于存在多形性，所以可根据自己的需要向系统里加入任意多的新类型，同时毋需更改true()方法。在一个设计良好的OOP程序中，我们的大多数或者所有方法都会遵从tune()的模型，而且只与基础类接口通信。我们说这样的程序具有“扩展性”，因为可以从通用的基础类继承新的数据类型，从而新添一些功能。如果是为了适应新类的要求，那么对基础类接口进行操纵的方法根本不需要改变，</p>

  <p>对于乐器例子，假设我们在基础类里加入更多的方法，以及一系列新类，那么会出现什么情况呢？下面是示意图：</p>

  <p>所有这些新类都能与老类――tune()默契地工作，毋需对tune()作任何调整。即使tune()位于一个独立的文件里，而将新方法添加到Instrument的接口，tune()也能正确地工作，不需要重新编译。下面这个程序是对上述示意图的具体实现：</p>

  <p>//: Music3.java</p>

  <p>// An extensible program</p>

  <p>import java.util.*;</p>

  <p>class Instrument3 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Instrument3.play()");</p>

  <p>}</p>

  <p>public String what() {</p>

  <p>return "Instrument3";</p>

  <p>}</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Wind3 extends Instrument3 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Wind3.play()");</p>

  <p>}</p>

  <p>public String what() { return "Wind3"; }</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Percussion3 extends Instrument3 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Percussion3.play()");</p>

  <p>}</p>

  <p>public String what() { return "Percussion3"; }</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Stringed3 extends Instrument3 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Stringed3.play()");</p>

  <p>}</p>

  <p>public String what() { return "Stringed3"; }</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Brass3 extends Wind3 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Brass3.play()");</p>

  <p>}</p>

  <p>public void adjust() {</p>

  <p>System.out.println("Brass3.adjust()");</p>

  <p>}</p>

  <p>}</p>

  <p>class Woodwind3 extends Wind3 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Woodwind3.play()");</p>

  <p>}</p>

  <p>public String what() { return "Woodwind3"; }</p>

  <p>}</p>

  <p>public class Music3 {</p>

  <p>// Doesn't care about type, so new types</p>

  <p>// added to the system still work right:</p>

  <p>static void tune(Instrument3 i) {</p>

  <p>// ...</p>

  <p>i.play();</p>

  <p>}</p>

  <p>static void tuneAll(Instrument3[] e) {</p>

  <p>for(int i = 0; i &lt; e.length; i++)</p>

  <p>tune(e[i]);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Instrument3[] orchestra = new Instrument3[5];</p>

  <p>int i = 0;</p>

  <p>// Upcasting during addition to the array:</p>

  <p>orchestra[i++] = new Wind3();</p>

  <p>orchestra[i++] = new Percussion3();</p>

  <p>orchestra[i++] = new Stringed3();</p>

  <p>orchestra[i++] = new Brass3();</p>

  <p>orchestra[i++] = new Woodwind3();</p>

  <p>tuneAll(orchestra);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>新方法是what()和adjust()。前者返回一个String句柄，同时返回对那个类的说明；后者使我们能对每种乐器进行调整。</p>

  <p>在main()中，当我们将某样东西置入Instrument3数组时，就会自动上溯造型到Instrument3。</p>

  <p>可以看到，在围绕tune()方法的其他所有代码都发生变化的同时，tune()方法却丝毫不受它们的影响，依然故我地正常工作。这正是利用多形性希望达到的目标。我们对代码进行修改后，不会对程序中不应受到影响的部分造成影响。此外，我们认为多形性是一种至关重要的技术，它允许程序员“将发生改变的东西同没有发生改变的东西区分开”。</p>

  <p>7.3 覆盖与过载</p>

  <p>现在让我们用不同的眼光来看看本章的头一个例子。在下面这个程序中，方法play()的接口会在被覆盖的过程中发生变化。这意味着我们实际并没有“覆盖”方法，而是使其“过载”。编译器允许我们对方法进行过载处理，使其不报告出错。但这种行为可能并不是我们所希望的。下面是这个例子：</p>

  <p>//: WindError.java</p>

  <p>// Accidentally changing the interface</p>

  <p>class NoteX {</p>

  <p>public static final int</p>

  <p>MIDDLE_C = 0, C_SHARP = 1, C_FLAT = 2;</p>

  <p>}</p>

  <p>class InstrumentX {</p>

  <p>public void play(int NoteX) {</p>

  <p>System.out.println("InstrumentX.play()");</p>

  <p>}</p>

  <p>}</p>

  <p>class WindX extends InstrumentX {</p>

  <p>// OOPS! Changes the method interface:</p>

  <p>public void play(NoteX n) {</p>

  <p>System.out.println("WindX.play(NoteX n)");</p>

  <p>}</p>

  <p>}</p>

  <p>public class WindError {</p>

  <p>public static void tune(InstrumentX i) {</p>

  <p>// ...</p>

  <p>i.play(NoteX.MIDDLE_C);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>WindX flute = new WindX();</p>

  <p>tune(flute); // Not the desired behavior!</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这里还向大家引入了另一个易于混淆的概念。在InstrumentX中，play()方法采用了一个int（整数）数值，它的标识符是NoteX。也就是说，即使NoteX是一个类名，也可以把它作为一个标识符使用，编译器不会报告出错。但在WindX中，play()采用一个NoteX句柄，它有一个标识符n。即便我们使用“play(NoteX NoteX)”，编译器也不会报告错误。这样一来，看起来就象是程序员有意覆盖play()的功能，但对方法的类型定义却稍微有些不确切。然而，编译器此时假定的是程序员有意进行“过载”，而非“覆盖”。请仔细体会这两个术语的区别。“过载”是指同一样东西在不同的地方具有多种含义；而“覆盖”是指它随时随地都只有一种含义，只是原先的含义完全被后来的含义取代了。请注意如果遵守标准的Java命名规范，自变量标识符就应该是noteX，这样可把它与类名区分开。</p>

  <p>在tune中，“InstrumentX i”会发出play()消息，同时将某个NoteX成员作为自变量使用（MIDDLE_C）。由于NoteX包含了int定义，过载的play()方法的int版本会得到调用。同时由于它尚未被“覆盖”，所以会使用基础类版本。</p>

  <p>输出是：</p>

  <p>InstrumentX.play()</p>

  <p>7.4 抽象类和方法</p>

  <p>在我们所有乐器（Instrument）例子中，基础类Instrument内的方法都肯定是“伪”方法。若去调用这些方法，就会出现错误。那是由于Instrument的意图是为从它衍生出去的所有类都创建一个通用接口。</p>

  <p>之所以要建立这个通用接口，唯一的原因就是它能为不同的子类型作出不同的表示。它为我们建立了一种基本形式，使我们能定义在所有衍生类里“通用”的一些东西。为阐述这个观念，另一个方法是把Instrument称为“抽象基础类”（简称“抽象类”）。若想通过该通用接口处理一系列类，就需要创建一个抽象类。对所有与基础类声明的签名相符的衍生类方法，都可以通过动态绑定机制进行调用（然而，正如上一节指出的那样，如果方法名与基础类相同，但自变量或参数不同，就会出现过载现象，那或许并非我们所愿意的）。</p>

  <p>如果有一个象Instrument那样的抽象类，那个类的对象几乎肯定没有什么意义。换言之，Instrument的作用仅仅是表达接口，而不是表达一些具体的实施细节。所以创建一个Instrument对象是没有意义的，而且我们通常都应禁止用户那样做。为达到这个目的，可令Instrument内的所有方法都显示出错消息。但这样做会延迟信息到运行期，并要求在用户那一面进行彻底、可靠的测试。无论如何，最好的方法都是在编译期间捕捉到问题。</p>

  <p>针对这个问题，Java专门提供了一种机制，名为“抽象方法”。它属于一种不完整的方法，只含有一个声明，没有方法主体。下面是抽象方法声明时采用的语法：</p>

  <p>abstract void X();</p>

  <p>包含了抽象方法的一个类叫作“抽象类”。如果一个类里包含了一个或多个抽象方法，类就必须指定成abstract（抽象）。否则，编译器会向我们报告一条出错消息。</p>

  <p>若一个抽象类是不完整的，那么一旦有人试图生成那个类的一个对象，编译器又会采取什么行动呢？由于不能安全地为一个抽象类创建属于它的对象，所以会从编译器那里获得一条出错提示。通过这种方法，编译器可保证抽象类的“纯洁性”，我们不必担心会误用它。</p>

  <p>如果从一个抽象类继承，而且想生成新类型的一个对象，就必须为基础类中的所有抽象方法提供方法定义。如果不这样做（完全可以选择不做），则衍生类也会是抽象的，而且编译器会强迫我们用abstract关键字标志那个类的“抽象”本质。</p>

  <p>即使不包括任何abstract方法，亦可将一个类声明成“抽象类”。如果一个类没必要拥有任何抽象方法，而且我们想禁止那个类的所有实例，这种能力就会显得非常有用。</p>

  <p>Instrument类可很轻松地转换成一个抽象类。只有其中一部分方法会变成抽象方法，因为使一个类抽象以后，并不会强迫我们将它的所有方法都同时变成抽象。下面是它看起来的样子：</p>

  <p>下面是我们修改过的“管弦”乐器例子，其中采用了抽象类以及方法：</p>

  <p>//: Music4.java</p>

  <p>// Abstract classes and methods</p>

  <p>import java.util.*;</p>

  <p>abstract class Instrument4 {</p>

  <p>int i; // storage allocated for each</p>

  <p>public abstract void play();</p>

  <p>public String what() {</p>

  <p>return "Instrument4";</p>

  <p>}</p>

  <p>public abstract void adjust();</p>

  <p>}</p>

  <p>class Wind4 extends Instrument4 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Wind4.play()");</p>

  <p>}</p>

  <p>public String what() { return "Wind4"; }</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Percussion4 extends Instrument4 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Percussion4.play()");</p>

  <p>}</p>

  <p>public String what() { return "Percussion4"; }</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Stringed4 extends Instrument4 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Stringed4.play()");</p>

  <p>}</p>

  <p>public String what() { return "Stringed4"; }</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Brass4 extends Wind4 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Brass4.play()");</p>

  <p>}</p>

  <p>public void adjust() {</p>

  <p>System.out.println("Brass4.adjust()");</p>

  <p>}</p>

  <p>}</p>

  <p>class Woodwind4 extends Wind4 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Woodwind4.play()");</p>

  <p>}</p>

  <p>public String what() { return "Woodwind4"; }</p>

  <p>}</p>

  <p>public class Music4 {</p>

  <p>// Doesn't care about type, so new types</p>

  <p>// added to the system still work right:</p>

  <p>static void tune(Instrument4 i) {</p>

  <p>// ...</p>

  <p>i.play();</p>

  <p>}</p>

  <p>static void tuneAll(Instrument4[] e) {</p>

  <p>for(int i = 0; i &lt; e.length; i++)</p>

  <p>tune(e[i]);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Instrument4[] orchestra = new Instrument4[5];</p>

  <p>int i = 0;</p>

  <p>// Upcasting during addition to the array:</p>

  <p>orchestra[i++] = new Wind4();</p>

  <p>orchestra[i++] = new Percussion4();</p>

  <p>orchestra[i++] = new Stringed4();</p>

  <p>orchestra[i++] = new Brass4();</p>

  <p>orchestra[i++] = new Woodwind4();</p>

  <p>tuneAll(orchestra);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>可以看出，除基础类以外，实际并没有进行什么改变。</p>

  <p>创建抽象类和方法有时对我们非常有用，因为它们使一个类的抽象变成明显的事实，可明确告诉用户和编译器自己打算如何用它。</p>

  <p>7.5 接口</p>

  <p>“interface”（接口）关键字使抽象的概念更深入了一层。我们可将其想象为一个“纯”抽象类。它允许创建者规定一个类的基本形式：方法名、自变量列表以及返回类型，但不规定方法主体。接口也包含了基本数据类型的数据成员，但它们都默认为static和final。接口只提供一种形式，并不提供实施的细节。</p>

  <p>接口这样描述自己：“对于实现我的所有类，看起来都应该象我现在这个样子”。因此，采用了一个特定接口的所有代码都知道对于那个接口可能会调用什么方法。这便是接口的全部含义。所以我们常把接口用于建立类和类之间的一个“协议”。有些面向对象的程序设计语言采用了一个名为“protocol”（协议）的关键字，它做的便是与接口相同的事情。</p>

  <p>为创建一个接口，请使用interface关键字，而不要用class。与类相似，我们可在interface关键字的前面增加一个public关键字（但只有接口定义于同名的一个文件内）；或者将其省略，营造一种“友好的”状态。</p>

  <p>为了生成与一个特定的接口（或一组接口）相符的类，要使用implements（实现）关键字。我们要表达的意思是“接口看起来就象那个样子，这儿是它具体的工作细节”。除这些之外，我们其他的工作都与继承极为相似。下面是乐器例子的示意图：</p>

  <p>具体实现了一个接口以后，就获得了一个普通的类，可用标准方式对其进行扩展。</p>

  <p>可决定将一个接口中的方法声明明确定义为“public”。但即便不明确定义，它们也会默认为public。所以在实现一个接口的时候，来自接口的方法必须定义成public。否则的话，它们会默认为“友好的”，而且会限制我们在继承过程中对一个方法的访问――Java编译器不允许我们那样做。</p>

  <p>在Instrument例子的修改版本中，大家可明确地看出这一点。注意接口中的每个方法都严格地是一个声明，它是编译器唯一允许的。除此以外，Instrument5中没有一个方法被声明为public，但它们都会自动获得public属性。如下所示：</p>

  <p>//: Music5.java</p>

  <p>// Interfaces</p>

  <p>import java.util.*;</p>

  <p>interface Instrument5 {</p>

  <p>// Compile-time constant:</p>

  <p>int i = 5; // static &amp; final</p>

  <p>// Cannot have method definitions:</p>

  <p>void play(); // Automatically public</p>

  <p>String what();</p>

  <p>void adjust();</p>

  <p>}</p>

  <p>class Wind5 implements Instrument5 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Wind5.play()");</p>

  <p>}</p>

  <p>public String what() { return "Wind5"; }</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Percussion5 implements Instrument5 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Percussion5.play()");</p>

  <p>}</p>

  <p>public String what() { return "Percussion5"; }</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Stringed5 implements Instrument5 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Stringed5.play()");</p>

  <p>}</p>

  <p>public String what() { return "Stringed5"; }</p>

  <p>public void adjust() {}</p>

  <p>}</p>

  <p>class Brass5 extends Wind5 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Brass5.play()");</p>

  <p>}</p>

  <p>public void adjust() {</p>

  <p>System.out.println("Brass5.adjust()");</p>

  <p>}</p>

  <p>}</p>

  <p>class Woodwind5 extends Wind5 {</p>

  <p>public void play() {</p>

  <p>System.out.println("Woodwind5.play()");</p>

  <p>}</p>

  <p>public String what() { return "Woodwind5"; }</p>

  <p>}</p>

  <p>public class Music5 {</p>

  <p>// Doesn't care about type, so new types</p>

  <p>// added to the system still work right:</p>

  <p>static void tune(Instrument5 i) {</p>

  <p>// ...</p>

  <p>i.play();</p>

  <p>}</p>

  <p>static void tuneAll(Instrument5[] e) {</p>

  <p>for(int i = 0; i &lt; e.length; i++)</p>

  <p>tune(e[i]);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Instrument5[] orchestra = new Instrument5[5];</p>

  <p>int i = 0;</p>

  <p>// Upcasting during addition to the array:</p>

  <p>orchestra[i++] = new Wind5();</p>

  <p>orchestra[i++] = new Percussion5();</p>

  <p>orchestra[i++] = new Stringed5();</p>

  <p>orchestra[i++] = new Brass5();</p>

  <p>orchestra[i++] = new Woodwind5();</p>

  <p>tuneAll(orchestra);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>代码剩余的部分按相同的方式工作。我们可以自由决定上溯造型到一个名为Instrument5的“普通”类，一个名为Instrument5的“抽象”类，或者一个名为Instrument5的“接口”。所有行为都是相同的。事实上，我们在tune()方法中可以发现没有任何证据显示Instrument5到底是个“普通”类、“抽象”类还是一个“接口”。这是做是故意的：每种方法都使程序员能对对象的创建与使用进行不同的控制。</p>

  <p>7.5.1 Java的“多重继承”</p>

  <p>接口只是比抽象类“更纯”的一种形式。它的用途并不止那些。由于接口根本没有具体的实施细节――也就是说，没有与存储空间与“接口”关联在一起――所以没有任何办法可以防止多个接口合并到一起。这一点是至关重要的，因为我们经常都需要表达这样一个意思：“x从属于a，也从属于b，也从属于c”。在C++中，将多个类合并到一起的行动称作“多重继承”，而且操作较为不便，因为每个类都可能有一套自己的实施细节。在Java中，我们可采取同样的行动，但只有其中一个类拥有具体的实施细节。所以在合并多个接口的时候，C++的问题不会在Java中重演。如下所示：</p>

  <p>在一个衍生类中，我们并不一定要拥有一个抽象或具体（没有抽象方法）的基础类。如果确实想从一个非接口继承，那么只能从一个继承。剩余的所有基本元素都必须是“接口”。我们将所有接口名置于implements关键字的后面，并用逗号分隔它们。可根据需要使用多个接口，而且每个接口都会成为一个独立的类型，可对其进行上溯造型。下面这个例子展示了一个“具体”类同几个接口合并的情况，它最终生成了一个新类：</p>

  <p>//: Adventure.java</p>

  <p>// Multiple interfaces</p>

  <p>import java.util.*;</p>

  <p>interface CanFight {</p>

  <p>void fight();</p>

  <p>}</p>

  <p>interface CanSwim {</p>

  <p>void swim();</p>

  <p>}</p>

  <p>interface CanFly {</p>

  <p>void fly();</p>

  <p>}</p>

  <p>class ActionCharacter {</p>

  <p>public void fight() {}</p>

  <p>}</p>

  <p>class Hero extends ActionCharacter</p>

  <p>implements CanFight, CanSwim, CanFly {</p>

  <p>public void swim() {}</p>

  <p>public void fly() {}</p>

  <p>}</p>

  <p>public class Adventure {</p>

  <p>static void t(CanFight x) { x.fight(); }</p>

  <p>static void u(CanSwim x) { x.swim(); }</p>

  <p>static void v(CanFly x) { x.fly(); }</p>

  <p>static void w(ActionCharacter x) { x.fight(); }</p>

  <p>public static void main(String[] args) {</p>

  <p>Hero i = new Hero();</p>

  <p>t(i); // Treat it as a CanFight</p>

  <p>u(i); // Treat it as a CanSwim</p>

  <p>v(i); // Treat it as a CanFly</p>

  <p>w(i); // Treat it as an ActionCharacter</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>从中可以看到，Hero将具体类ActionCharacter同接口CanFight，CanSwim以及CanFly合并起来。按这种形式合并一个具体类与接口的时候，具体类必须首先出现，然后才是接口（否则编译器会报错）。</p>

  <p>请注意fight()的签名在CanFight接口与ActionCharacter类中是相同的，而且没有在Hero中为fight()提供一个具体的定义。接口的规则是：我们可以从它继承（稍后就会看到），但这样得到的将是另一个接口。如果想创建新类型的一个对象，它就必须是已提供所有定义的一个类。尽管Hero没有为fight()明确地提供一个定义，但定义是随同ActionCharacter来的，所以这个定义会自动提供，我们可以创建Hero的对象。</p>

  <p>在类Adventure中，我们可看到共有四个方法，它们将不同的接口和具体类作为自己的自变量使用。创建一个Hero对象后，它可以传递给这些方法中的任何一个。这意味着它们会依次上溯造型到每一个接口。由于接口是用Java设计的，所以这样做不会有任何问题，而且程序员不必对此加以任何特别的关注。</p>

  <p>注意上述例子已向我们揭示了接口最关键的作用，也是使用接口最重要的一个原因：能上溯造型至多个基础类。使用接口的第二个原因与使用抽象基础类的原因是一样的：防止客户程序员制作这个类的一个对象，以及规定它仅仅是一个接口。这样便带来了一个问题：到底应该使用一个接口还是一个抽象类呢？若使用接口，我们可以同时获得抽象类以及接口的好处。所以假如想创建的基础类没有任何方法定义或者成员变量，那么无论如何都愿意使用接口，而不要选择抽象类。事实上，如果事先知道某种东西会成为基础类，那么第一个选择就是把它变成一个接口。只有在必须使用方法定义或者成员变量的时候，才应考虑采用抽象类。</p>

  <p>7.5.2 通过继承扩展接口</p>

  <p>利用继承技术，可方便地为一个接口添加新的方法声明，也可以将几个接口合并成一个新接口。在这两种情况下，最终得到的都是一个新接口，如下例所示：</p>

  <p>//: HorrorShow.java</p>

  <p>// Extending an interface with inheritance</p>

  <p>interface Monster {</p>

  <p>void menace();</p>

  <p>}</p>

  <p>interface DangerousMonster extends Monster {</p>

  <p>void destroy();</p>

  <p>}</p>

  <p>interface Lethal {</p>

  <p>void kill();</p>

  <p>}</p>

  <p>class DragonZilla implements DangerousMonster {</p>

  <p>public void menace() {}</p>

  <p>public void destroy() {}</p>

  <p>}</p>

  <p>interface Vampire</p>

  <p>extends DangerousMonster, Lethal {</p>

  <p>void drinkBlood();</p>

  <p>}</p>

  <p>class HorrorShow {</p>

  <p>static void u(Monster b) { b.menace(); }</p>

  <p>static void v(DangerousMonster d) {</p>

  <p>d.menace();</p>

  <p>d.destroy();</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>DragonZilla if2 = new DragonZilla();</p>

  <p>u(if2);</p>

  <p>v(if2);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>DangerousMonster是对Monster的一个简单的扩展，最终生成了一个新接口。这是在DragonZilla里实现的。</p>

  <p>Vampire的语法仅在继承接口时才可使用。通常，我们只能对单独一个类应用extends（扩展）关键字。但由于接口可能由多个其他接口构成，所以在构建一个新接口时，extends可能引用多个基础接口。正如大家看到的那样，接口的名字只是简单地使用逗号分隔。</p>

  <p>7.5.3 常数分组</p>

  <p>由于置入一个接口的所有字段都自动具有static和final属性，所以接口是对常数值进行分组的一个好工具，它具有与C或C++的enum非常相似的效果。如下例所示：</p>

  <p>//: Months.java</p>

  <p>// Using interfaces to create groups of constants</p>

  <p>package c07;</p>

  <p>public interface Months {</p>

  <p>int</p>

  <p>JANUARY = 1, FEBRUARY = 2, MARCH = 3,</p>

  <p>APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,</p>

  <p>AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,</p>

  <p>NOVEMBER = 11, DECEMBER = 12;</p>

  <p>} ///:~</p>

  <p>注意根据Java命名规则，拥有固定标识符的static final基本数据类型（亦即编译期常数）都全部采用大写字母（用下划线分隔单个标识符里的多个单词）。</p>

  <p>接口中的字段会自动具备public属性，所以没必要专门指定。</p>

  <p>现在，通过导入c07.*或c07.Months，我们可以从包的外部使用常数――就象对其他任何包进行的操作那样。此外，也可以用类似Months.JANUARY的表达式对值进行引用。当然，我们获得的只是一个int，所以不象C++的enum那样拥有额外的类型安全性。但与将数字强行编码（硬编码）到自己的程序中相比，这种（常用的）技术无疑已经是一个巨大的进步。我们通常把“硬编码”数字的行为称为“魔术数字”，它产生的代码是非常难以维护的。</p>

  <p>如确实不想放弃额外的类型安全性，可构建象下面这样的一个类（注释①）：</p>

  <p>//: Month2.java</p>

  <p>// A more robust enumeration system</p>

  <p>package c07;</p>

  <p>public final class Month2 {</p>

  <p>private String name;</p>

  <p>private Month2(String nm) { name = nm; }</p>

  <p>public String toString() { return name; }</p>

  <p>public final static Month2</p>

  <p>JAN = new Month2("January"),</p>

  <p>FEB = new Month2("February"),</p>

  <p>MAR = new Month2("March"),</p>

  <p>APR = new Month2("April"),</p>

  <p>MAY = new Month2("May"),</p>

  <p>JUN = new Month2("June"),</p>

  <p>JUL = new Month2("July"),</p>

  <p>AUG = new Month2("August"),</p>

  <p>SEP = new Month2("September"),</p>

  <p>OCT = new Month2("October"),</p>

  <p>NOV = new Month2("November"),</p>

  <p>DEC = new Month2("December");</p>

  <p>public final static Month2[] month = {</p>

  <p>JAN, JAN, FEB, MAR, APR, MAY, JUN,</p>

  <p>JUL, AUG, SEP, OCT, NOV, DEC</p>

  <p>};</p>

  <p>public static void main(String[] args) {</p>

  <p>Month2 m = Month2.JAN;</p>

  <p>System.out.println(m);</p>

  <p>m = Month2.month[12];</p>

  <p>System.out.println(m);</p>

  <p>System.out.println(m == Month2.DEC);</p>

  <p>System.out.println(m.equals(Month2.DEC));</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>①：是Rich Hoffarth的一封E-mail触发了我这样编写程序的灵感。</p>

  <p>这个类叫作Month2，因为标准Java库里已经有一个Month。它是一个final类，并含有一个private构建器，所以没有人能从它继承，或制作它的一个实例。唯一的实例就是那些final static对象，它们是在类本身内部创建的，包括：JAN，FEB，MAR等等。这些对象也在month数组中使用，后者让我们能够按数字挑选月份，而不是按名字（注意数组中提供了一个多余的JAN，使偏移量增加了1，也使December确实成为12月）。在main()中，我们可注意到类型的安全性：m是一个Month2对象，所以只能将其分配给Month2。在前面的Months.java例子中，只提供了int值，所以本来想用来代表一个月份的int变量可能实际获得一个整数值，那样做可能不十分安全。</p>

  <p>这儿介绍的方法也允许我们交换使用==或者equals()，就象main()尾部展示的那样。</p>

  <p>7.5.4 初始化接口中的字段</p>

  <p>接口中定义的字段会自动具有static和final属性。它们不能是“空白final”，但可初始化成非常数表达式。例如：</p>

  <p>//: RandVals.java</p>

  <p>// Initializing interface fields with</p>

  <p>// non-constant initializers</p>

  <p>import java.util.*;</p>

  <p>public interface RandVals {</p>

  <p>int rint = (int)(Math.random() * 10);</p>

  <p>long rlong = (long)(Math.random() * 10);</p>

  <p>float rfloat = (float)(Math.random() * 10);</p>

  <p>double rdouble = Math.random() * 10;</p>

  <p>} ///:~</p>

  <p>由于字段是static的，所以它们会在首次装载类之后、以及首次访问任何字段之前获得初始化。下面是一个简单的测试：</p>

  <p>//: TestRandVals.java</p>

  <p>public class TestRandVals {</p>

  <p>public static void main(String[] args) {</p>

  <p>System.out.println(RandVals.rint);</p>

  <p>System.out.println(RandVals.rlong);</p>

  <p>System.out.println(RandVals.rfloat);</p>

  <p>System.out.println(RandVals.rdouble);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>当然，字段并不是接口的一部分，而是保存于那个接口的static存储区域中。</p>

  <p>7.6 内部类</p>

  <p>在Java 1.1中，可将一个类定义置入另一个类定义中。这就叫作“内部类”。内部类对我们非常有用，因为利用它可对那些逻辑上相互联系的类进行分组，并可控制一个类在另一个类里的“可见性”。然而，我们必须认识到内部类与以前讲述的“合成”方法存在着根本的区别。</p>

  <p>通常，对内部类的需要并不是特别明显的，至少不会立即感觉到自己需要使用内部类。在本章的末尾，介绍完内部类的所有语法之后，大家会发现一个特别的例子。通过它应该可以清晰地认识到内部类的好处。</p>

  <p>创建内部类的过程是平淡无奇的：将类定义置入一个用于封装它的类内部（若执行这个程序遇到麻烦，请参见第3章的3.1.2小节“赋值”）：</p>

  <p>//: Parcel1.java</p>

  <p>// Creating inner classes</p>

  <p>package c07.parcel1;</p>

  <p>public class Parcel1 {</p>

  <p>class Contents {</p>

  <p>private int i = 11;</p>

  <p>public int value() { return i; }</p>

  <p>}</p>

  <p>class Destination {</p>

  <p>private String label;</p>

  <p>Destination(String whereTo) {</p>

  <p>label = whereTo;</p>

  <p>}</p>

  <p>String readLabel() { return label; }</p>

  <p>}</p>

  <p>// Using inner classes looks just like</p>

  <p>// using any other class, within Parcel1:</p>

  <p>public void ship(String dest) {</p>

  <p>Contents c = new Contents();</p>

  <p>Destination d = new Destination(dest);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel1 p = new Parcel1();</p>

  <p>p.ship("Tanzania");</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>若在ship()内部使用，内部类的使用看起来和其他任何类都没什么分别。在这里，唯一明显的区别就是它的名字嵌套在Parcel1里面。但大家不久就会知道，这其实并非唯一的区别。</p>

  <p>更典型的一种情况是，一个外部类拥有一个特殊的方法，它会返回指向一个内部类的句柄。就象下面这样：</p>

  <p>//: Parcel2.java</p>

  <p>// Returning a handle to an inner class</p>

  <p>package c07.parcel2;</p>

  <p>public class Parcel2 {</p>

  <p>class Contents {</p>

  <p>private int i = 11;</p>

  <p>public int value() { return i; }</p>

  <p>}</p>

  <p>class Destination {</p>

  <p>private String label;</p>

  <p>Destination(String whereTo) {</p>

  <p>label = whereTo;</p>

  <p>}</p>

  <p>String readLabel() { return label; }</p>

  <p>}</p>

  <p>public Destination to(String s) {</p>

  <p>return new Destination(s);</p>

  <p>}</p>

  <p>public Contents cont() {</p>

  <p>return new Contents();</p>

  <p>}</p>

  <p>public void ship(String dest) {</p>

  <p>Contents c = cont();</p>

  <p>Destination d = to(dest);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel2 p = new Parcel2();</p>

  <p>p.ship("Tanzania");</p>

  <p>Parcel2 q = new Parcel2();</p>

  <p>// Defining handles to inner classes:</p>

  <p>Parcel2.Contents c = q.cont();</p>

  <p>Parcel2.Destination d = q.to("Borneo");</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>若想在除外部类非static方法内部之外的任何地方生成内部类的一个对象，必须将那个对象的类型设为“外部类名.内部类名”，就象main()中展示的那样。</p>

  <p>7.6.1 内部类和上溯造型</p>

  <p>迄今为止，内部类看起来仍然没什么特别的地方。毕竟，用它实现隐藏显得有些大题小做。Java已经有一个非常优秀的隐藏机制――只允许类成为“友好的”（只在一个包内可见），而不是把它创建成一个内部类。</p>

  <p>然而，当我们准备上溯造型到一个基础类（特别是到一个接口）的时候，内部类就开始发挥其关键作用（从用于实现的对象生成一个接口句柄具有与上溯造型至一个基础类相同的效果）。这是由于内部类随后可完全进入不可见或不可用状态――对任何人都将如此。所以我们可以非常方便地隐藏实施细节。我们得到的全部回报就是一个基础类或者接口的句柄，而且甚至有可能不知道准确的类型。就象下面这样：</p>

  <p>//: Parcel3.java</p>

  <p>// Returning a handle to an inner class</p>

  <p>package c07.parcel3;</p>

  <p>abstract class Contents {</p>

  <p>abstract public int value();</p>

  <p>}</p>

  <p>interface Destination {</p>

  <p>String readLabel();</p>

  <p>}</p>

  <p>public class Parcel3 {</p>

  <p>private class PContents extends Contents {</p>

  <p>private int i = 11;</p>

  <p>public int value() { return i; }</p>

  <p>}</p>

  <p>protected class PDestination</p>

  <p>implements Destination {</p>

  <p>private String label;</p>

  <p>private PDestination(String whereTo) {</p>

  <p>label = whereTo;</p>

  <p>}</p>

  <p>public String readLabel() { return label; }</p>

  <p>}</p>

  <p>public Destination dest(String s) {</p>

  <p>return new PDestination(s);</p>

  <p>}</p>

  <p>public Contents cont() {</p>

  <p>return new PContents();</p>

  <p>}</p>

  <p>}</p>

  <p>class Test {</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel3 p = new Parcel3();</p>

  <p>Contents c = p.cont();</p>

  <p>Destination d = p.dest("Tanzania");</p>

  <p>// Illegal -- can't access private class:</p>

  <p>//! Parcel3.PContents c = p.new PContents();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>现在，Contents和Destination代表可由客户程序员使用的接口（记住接口会将自己的所有成员都变成public属性）。为方便起见，它们置于单独一个文件里，但原始的Contents和Destination在它们自己的文件中是相互public的。</p>

  <p>在Parcel3中，一些新东西已经加入：内部类PContents被设为private，所以除了Parcel3之外，其他任何东西都不能访问它。PDestination被设为protected，所以除了Parcel3，Parcel3包内的类（因为protected也为包赋予了访问权；也就是说，protected也是“友好的”），以及Parcel3的继承者之外，其他任何东西都不能访问PDestination。这意味着客户程序员对这些成员的认识与访问将会受到限制。事实上，我们甚至不能下溯造型到一个private内部类（或者一个protected内部类，除非自己本身便是一个继承者），因为我们不能访问名字，就象在classTest里看到的那样。所以，利用private内部类，类设计人员可完全禁止其他人依赖类型编码，并可将具体的实施细节完全隐藏起来。除此以外，从客户程序员的角度来看，一个接口的范围没有意义的，因为他们不能访问不属于公共接口类的任何额外方法。这样一来，Java编译器也有机会生成效率更高的代码。</p>

  <p>普通（非内部）类不可设为private或protected――只允许public或者“友好的”。</p>

  <p>注意Contents不必成为一个抽象类。在这儿也可以使用一个普通类，但这种设计最典型的起点依然是一个“接口”。</p>

  <p>7.6.2 方法和作用域中的内部类</p>

  <p>至此，我们已基本理解了内部类的典型用途。对那些涉及内部类的代码，通常表达的都是“单纯”的内部类，非常简单，且极易理解。然而，内部类的设计非常全面，不可避免地会遇到它们的其他大量用法――假若我们在一个方法甚至一个任意的作用域内创建内部类。有两方面的原因促使我们这样做：</p>

  <p>(1) 正如前面展示的那样，我们准备实现某种形式的接口，使自己能创建和返回一个句柄。</p>

  <p>(2) 要解决一个复杂的问题，并希望创建一个类，用来辅助自己的程序方案。同时不愿意把它公开。</p>

  <p>在下面这个例子里，将修改前面的代码，以便使用：</p>

  <p>(1) 在一个方法内定义的类</p>

  <p>(2) 在方法的一个作用域内定义的类</p>

  <p>(3) 一个匿名类，用于实现一个接口</p>

  <p>(4) 一个匿名类，用于扩展拥有非默认构建器的一个类</p>

  <p>(5) 一个匿名类，用于执行字段初始化</p>

  <p>(6) 一个匿名类，通过实例初始化进行构建（匿名内部类不可拥有构建器）</p>

  <p>所有这些都在innerscopes包内发生。首先，来自前述代码的通用接口会在它们自己的文件里获得定义，使它们能在所有的例子里使用：</p>

  <p>//: Destination.java</p>

  <p>package c07.innerscopes;</p>

  <p>interface Destination {</p>

  <p>String readLabel();</p>

  <p>} ///:~</p>

  <p>由于我们已认为Contents可能是一个抽象类，所以可采取下面这种更自然的形式，就象一个接口那样：</p>

  <p>//: Contents.java</p>

  <p>package c07.innerscopes;</p>

  <p>interface Contents {</p>

  <p>int value();</p>

  <p>} ///:~</p>

  <p>尽管是含有具体实施细节的一个普通类，但Wrapping也作为它所有衍生类的一个通用“接口”使用：</p>

  <p>//: Wrapping.java</p>

  <p>package c07.innerscopes;</p>

  <p>public class Wrapping {</p>

  <p>private int i;</p>

  <p>public Wrapping(int x) { i = x; }</p>

  <p>public int value() { return i; }</p>

  <p>} ///:~</p>

  <p>在上面的代码中，我们注意到Wrapping有一个要求使用自变量的构建器，这就使情况变得更加有趣了。</p>

  <p>第一个例子展示了如何在一个方法的作用域（而不是另一个类的作用域）中创建一个完整的类：</p>

  <p>//: Parcel4.java</p>

  <p>// Nesting a class within a method</p>

  <p>package c07.innerscopes;</p>

  <p>public class Parcel4 {</p>

  <p>public Destination dest(String s) {</p>

  <p>class PDestination</p>

  <p>implements Destination {</p>

  <p>private String label;</p>

  <p>private PDestination(String whereTo) {</p>

  <p>label = whereTo;</p>

  <p>}</p>

  <p>public String readLabel() { return label; }</p>

  <p>}</p>

  <p>return new PDestination(s);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel4 p = new Parcel4();</p>

  <p>Destination d = p.dest("Tanzania");</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>PDestination类属于dest()的一部分，而不是Parcel4的一部分（同时注意可为相同目录内每个类内部的一个内部类使用类标识符PDestination，这样做不会发生命名的冲突）。因此，PDestination不可从dest()的外部访问。请注意在返回语句中发生的上溯造型――除了指向基础类Destination的一个句柄之外，没有任何东西超出dest()的边界之外。当然，不能由于类PDestination的名字置于dest()内部，就认为在dest()返回之后PDestination不是一个有效的对象。</p>

  <p>下面这个例子展示了如何在任意作用域内嵌套一个内部类：</p>

  <p>//: Parcel5.java</p>

  <p>// Nesting a class within a scope</p>

  <p>package c07.innerscopes;</p>

  <p>public class Parcel5 {</p>

  <p>private void internalTracking(boolean b) {</p>

  <p>if(b) {</p>

  <p>class TrackingSlip {</p>

  <p>private String id;</p>

  <p>TrackingSlip(String s) {</p>

  <p>id = s;</p>

  <p>}</p>

  <p>String getSlip() { return id; }</p>

  <p>}</p>

  <p>TrackingSlip ts = new TrackingSlip("slip");</p>

  <p>String s = ts.getSlip();</p>

  <p>}</p>

  <p>// Can't use it here! Out of scope:</p>

  <p>//! TrackingSlip ts = new TrackingSlip("x");</p>

  <p>}</p>

  <p>public void track() { internalTracking(true); }</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel5 p = new Parcel5();</p>

  <p>p.track();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>TrackingSlip类嵌套于一个if语句的作用域内。这并不意味着类是有条件创建的――它会随同其他所有东西得到编译。然而，在定义它的那个作用域之外，它是不可使用的。除这些以外，它看起来和一个普通类并没有什么区别。</p>

  <p>下面这个例子看起来有些奇怪：</p>

  <p>//: Parcel6.java</p>

  <p>// A method that returns an anonymous inner class</p>

  <p>package c07.innerscopes;</p>

  <p>public class Parcel6 {</p>

  <p>public Contents cont() {</p>

  <p>return new Contents() {</p>

  <p>private int i = 11;</p>

  <p>public int value() { return i; }</p>

  <p>}; // Semicolon required in this case</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel6 p = new Parcel6();</p>

  <p>Contents c = p.cont();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>cont()方法同时合并了返回值的创建代码，以及用于表示那个返回值的类。除此以外，这个类是匿名的――它没有名字。而且看起来似乎更让人摸不着头脑的是，我们准备创建一个Contents对象：</p>

  <p>return new Contents()</p>

  <p>但在这之后，在遇到分号之前，我们又说：“等一等，让我先在一个类定义里再耍一下花招”：</p>

  <p>return new Contents() {</p>

  <p>private int i = 11;</p>

  <p>public int value() { return i; }</p>

  <p>};</p>

  <p>这种奇怪的语法要表达的意思是：“创建从Contents衍生出来的匿名类的一个对象”。由new表达式返回的句柄会自动上溯造型成一个Contents句柄。匿名内部类的语法其实要表达的是：</p>

  <p>class MyContents extends Contents {</p>

  <p>private int i = 11;</p>

  <p>public int value() { return i; }</p>

  <p>}</p>

  <p>return new MyContents();</p>

  <p>在匿名内部类中，Contents是用一个默认构建器创建的。下面这段代码展示了基础类需要含有自变量的一个构建器时做的事情：</p>

  <p>//: Parcel7.java</p>

  <p>// An anonymous inner class that calls the</p>

  <p>// base-class constructor</p>

  <p>package c07.innerscopes;</p>

  <p>public class Parcel7 {</p>

  <p>public Wrapping wrap(int x) {</p>

  <p>// Base constructor call:</p>

  <p>return new Wrapping(x) {</p>

  <p>public int value() {</p>

  <p>return super.value() * 47;</p>

  <p>}</p>

  <p>}; // Semicolon required</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel7 p = new Parcel7();</p>

  <p>Wrapping w = p.wrap(10);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>也就是说，我们将适当的自变量简单地传递给基础类构建器，在这儿表现为在“new Wrapping(x)”中传递x。匿名类不能拥有一个构建器，这和在调用super()时的常规做法不同。</p>

  <p>在前述的两个例子中，分号并不标志着类主体的结束（和C++不同）。相反，它标志着用于包含匿名类的那个表达式的结束。因此，它完全等价于在其他任何地方使用分号。</p>

  <p>若想对匿名内部类的一个对象进行某种形式的初始化，此时会出现什么情况呢？由于它是匿名的，没有名字赋给构建器，所以我们不能拥有一个构建器。然而，我们可在定义自己的字段时进行初始化：</p>

  <p>//: Parcel8.java</p>

  <p>// An anonymous inner class that performs</p>

  <p>// initialization. A briefer version</p>

  <p>// of Parcel5.java.</p>

  <p>package c07.innerscopes;</p>

  <p>public class Parcel8 {</p>

  <p>// Argument must be final to use inside</p>

  <p>// anonymous inner class:</p>

  <p>public Destination dest(final String dest) {</p>

  <p>return new Destination() {</p>

  <p>private String label = dest;</p>

  <p>public String readLabel() { retu</p>

  <div class="mbppagebreak"></div>
</body>
</html>
